package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
)

const configPath = "$HOME/.config/notes/notes.conf"

type Config struct {
	Project  string
	APIKey   string
	Database string
	Editor   string
}

type note struct {
	Key     string `json:"key"`
	Content string `json:"content"`
}

var (
	config Config
)

func usage() {
	println(
		`Notes v0.1.0: Save and read notes remotely.

Usage: notes [options] [command] [args]

Options:
  -h		show this help
  --help	show this help
  --version	show version info

Commands:
  add [key]	adds a new note with name key
  read [key]	reads the note key
  edit [key]	edits the note named key
  config	runs the configuration script, also on first run
  help		show this help

Examples:
  notes config		Run the configuration script
  notes			Show this help
  notes add test	Adds a new note named test
  notes list		Lists all notes
  notes read test	Reads the note named test

Report bugs to: bleemayer@gmail.com
Home page: <https://notabug.org/brain/notes/>
General help: <https://notabug.org/brain/notes/wiki>`,
	)
}

func version() {
	println(
		`Notes 0.1.0
Copyright (C) 2020 Brian Mayer.
License MIT: MIT License <https://opensource.org/licenses/MIT>
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.

Written by Brian Lee Mayer.`,
	)
}

func defaultRequest() http.Request {
	path, _ := url.Parse("https://database.deta.sh/v1/" + config.Project + "/" + config.Database)
	return http.Request{
		Method: http.MethodPost,
		URL:    path,
		Header: http.Header{
			"Content-Type": {"application/json"},
			"X-API-Key":    {config.APIKey},
		},
	}
}

func configure() {
	configFile, err := os.Open(os.ExpandEnv(configPath))
	if err != nil {
		configFile, _ = os.Create(os.ExpandEnv(configPath))
	}
	defer configFile.Close()

	println("Enter project ID:")
	fmt.Scanln(&config.Project)

	println("Enter API-Key:")
	fmt.Scanln(&config.APIKey)

	println("Enter your database name: [notes]")
	fmt.Scanln(&config.Database)
	if config.Database == "" {
		config.Database = "notes"
	}

	println("Enter your editor: [vi]")
	fmt.Scanln(&config.Editor)
	if config.Editor == "" {
		config.Editor = "vi"
	}
	defaultRequest()

	json.NewEncoder(configFile).Encode(config)
}

func readConfig() error {
	configFile, err := os.Open(os.ExpandEnv(configPath))
	if err != nil {
		return err
	}
	defer configFile.Close()

	json.NewDecoder(configFile).Decode(&config)
	defaultRequest()

	return nil
}

func composeNote(key string) {
	var notes struct {
		Items []note `json:"items"`
	}

	notes.Items = []note{{Key: url.PathEscape(key)}}

	cmd := exec.Command(config.Editor, ".content")
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	err := cmd.Run()
	if err != nil {
		panic(err)
	}

	data, err := ioutil.ReadFile(".content")
	os.Remove(".content")

	notes.Items[0].Content = string(data)
	content, err := json.Marshal(notes)
	if err != nil {
		panic(err)
	}

	reader := strings.NewReader(string(content))
	req := defaultRequest()
	req.URL.Path += "/items"
	req.Method = http.MethodPut
	req.Body = ioutil.NopCloser(reader)
	_, err = http.DefaultClient.Do(&req)
	if err != nil {
		panic(err)
	}
}

func getNote(key string) note {
	req := defaultRequest()
	req.URL.Path += "/items/" + url.PathEscape(key)
	req.Method = http.MethodGet
	res, err := http.DefaultClient.Do(&req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var n note
	json.NewDecoder(res.Body).Decode(&n)
	return n
}

func listNotes() {
	req := defaultRequest()
	req.URL.Path += "/query"
	res, err := http.DefaultClient.Do(&req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var notes struct {
		Items []note `json:"items"`
	}

	json.NewDecoder(res.Body).Decode(&notes)
	for _, n := range notes.Items {
		maxk := 20
		if len(n.Key) < 20 {
			maxk = len(n.Key)
		}
		maxc := 40
		if len(n.Content) < 40 {
			maxc = len(n.Content)
		}

		fmt.Printf("%-20s %-40q\n", n.Key[:maxk], n.Content[:maxc])
	}
}

func editNote(key string) {
	n := getNote(key)

	ioutil.WriteFile(".content", []byte(n.Content), os.ModePerm)
	composeNote(key)
}

func main() {
	// Configuration step
	if readConfig() != nil {
		configure()
	}

	if len(os.Args) < 2 {
		println("Error: missing argument")
		usage()
		os.Exit(1)
	}

	for i := 1; i < len(os.Args); i++ {
		switch os.Args[i] {
		case "-h", "help", "--help":
			usage()
			os.Exit(1)
		case "--version":
			version()
			os.Exit(1)
		case "list":
			listNotes()
		case "add":
			if len(os.Args) > 2 {
				i++
				composeNote(os.Args[i])
				i++
			}
		case "edit":
			if len(os.Args) > 2 {
				i++
				editNote(os.Args[i])
				i++
			}
		case "read":
			if len(os.Args) > 2 {
				i++
				println(getNote(os.Args[i]).Content)
				i++
			}
		case "config":
			configure()
			os.Exit(1)
		default:
			println("Error: unrecognized argument " + os.Args[i])
			usage()
		}
	}
}
